pin-diary-6-sd-dev_2022-12-31
前後のテンプレ行よりインデントが深い行はテンプレ判定しないとか/villagepump/Mijinko_SD.icon
単純に空行がやたらと誤反応していたってだけなので、空行だけ判別しないとか
・でもそれはちょっと場当たり的なのでもう少し一般化したい
・空行がよく出てくるって理由で空行だけ対応して、他によく出てくる行があった場合の対応をしないのは微妙
テンプレ行の下側だけ存在していても上側が無ければ新たに挿入するとか
・もしくはテンプレの全ての行が存在していない時点で既存行への追記は諦めて新たに挿入するとか
具体的な改善方法
1行単位でテンプレートの判別を行うと、似たような関係の無い行をテンプレートの一部として誤認してしまう可能性がある
元々このバグを避けるために修正したので、こうなるのは避けたい
「テンプレートの行を離してはいけない」を前提とするが、それだと文章を挿入したいときに困るので、ある程度条件を緩和する
結局離しても良いことにした(後述)
テンプレ
・挿入された文章
テンプレ
これぐらいを許容したい
インデントが深い行は分断されているとみなさないという条件
別にインデントが深い必要はない?
必要ない気がしたのでインデントは加味しないようにする
インデントルールとかが暗黙的にあると編集する側にとってはわかりにくくなるし、こういった条件は極力無い方がいいと思う
テンプレ
・(無関係の元からあった文章)
・(無関係な文章その2)
テンプレ
でも、元のアルゴリズムに沿っているとこうなる可能性もある
1行ごとにテンプレ行を判別して、足りていない行だけを追記するという形だったため
「一定の許容されている条件を除いて分断されていないテンプレートが存在しない場合は、新たに挿入し直す」といった形にしよう
1つ前のdevバージョンでは条件云々についてが無く、「分断されていないテンプレートが存在しない場合は、新たに挿入し直す」というものだった
というわけで、テンプレート挿入の条件は以下のようにする
テンプレート行が無い場合は、所定の位置に新しいテンプレート行を挿入する
テンプレート行が分断されるのは許容する
ただし、テンプレートが全行見つからない場合は「テンプレート行が無い」ものと見なし、全てのテンプレート行を挿入し直す
bundle
code:script.ts
import { launch } from "./mod.ts";
import {
makeDiary, isOldDiary,
} from "../../villagepump/pin-diary-6%2Ftemplate/mod.ts";
launch(
"villagepump",
{
makeDiary,
isOldDiary,
},
);
5依存
format.ts
code:format.ts
import { findSplitIndex, patchLines } from "./util.ts";
// linesにタイトルを入れないように
export function patchTemplate(
lines: string[],
headers: string[],
footers: string[],
): string[] {
// headerとfooterに相当する行を補う
let bodies: string[] = lines;
bodies = patchLines(bodies, headers, "head");
bodies = patchLines(bodies, footers, "tail");
// headerとfooterの間に余裕をもたせる
const headerEnd = findSplitIndex(bodies, headers);
const footerEnd = findSplitIndex(bodies, footers);
if (headerEnd === null || footerEnd === null) {
// バグってテンプレの挿入がうまくいかなかったら諦める
console.log(
"format.tsのコードがバグったためtemplateの挿入を諦めました。\n" +
headerEnd: ${headerEnd}, footerEnd: ${footerEnd},
);
return lines;
}
const footerStart = footerEnd + 1 - footers.length;
return [
...bodies.slice(0, headerEnd + 1),
"",
...bodies.slice(headerEnd + 1, footerStart).join("\n").trim().split("\n"),
"",
...bodies.slice(footerStart),
];
}
util.ts
14行目のelseの分岐先をコメントアウトして、何もしないようにした
この部分は後で削除しても問題なさそう
code:util.ts
export function patchLines(
lines: string[],
appends: string[],
position: "head" | "tail" = "head",
): string[] {
let appendsIndex = 0;
for (const line of lines) {
if (appendsIndex == appends.length - 1) {
// appendsと完全に一致するパターンが発見されたので行を挿入せずに終了する
return lines;
}
++appendsIndex;
} else {
// 何もしない
// appendsIndex = 0;
}
}
// 一致するパターンが見つからなかった場合はappendsを挿入する
switch (position) {
case "head":
case "tail":
}
}
/** queryに合致するlinesの終了行を返す */
export function findSplitIndex(
lines: string[],
query: string[],
): number | null {
let queryCount = 0;
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
if (queryCount == query.length - 1) return Number(lineIndex);
++queryCount;
}
}
return null;
}
6依存(変更済み)
deps.ts
formatの読み込み元だけ書き換えた
code:deps.ts
// export { patchTemplate as format } from "../pin-diary-5/format.ts";
export { patchTemplate as format } from "./format.ts";
export {
pin,
unpin,
patch,
useStatusBar,
makeSocket,
disconnect,
listPages,
export type {
Socket,
export {
sleep,
export type {
Scrapbox,
export type {
BasePage,
6依存(未変更)
mod.ts
code:mod.ts
/// <reference no-default-lib="true"/>
/// <reference lib="esnext"/>
/// <reference lib="dom"/>
import {
pin,
unpin,
patch,
useStatusBar,
sleep,
makeSocket,
disconnect,
format,
} from "./deps.ts";
import { listPinnedPages } from "./list.ts";
import type { Scrapbox, Socket } from "./deps.ts";
declare const scrapbox: Scrapbox;
export interface DiaryInit {
/** 与えられた日付の日記ページのテンプレートを作る */
makeDiary: (date: Date) => {
title: string;
header: string[];
footer: string[];
},
/** 今日以外の日記ページかどうかを判断する函数
*
* @param title 判断対象のページタイトル
* @param today 今日の日付
* @param 今日以外の日記ページならtrue, それ以外のページは false
*/
isOldDiary: (title: string, today: Date) => boolean;
}
// initialize
export function launch(
project: string,
init: DiaryInit & { interval?: number },
) {
const interval = init.interval ?? 24 * 3600 * 1000;
const handleChange = () =>
scrapbox.Project.name === project ?
startObserve(project, interval, init) :
endObserve();
handleChange();
scrapbox.addListener("project:changed", handleChange);
}
let updateTimer: number | undefined;
async function startObserve(
project: string,
interval: number,
init: DiaryInit,
) {
endObserve();
await pinDiary(project, new Date(), init);
updateTimer = setInterval(
() => pinDiary(project, new Date(), init),
interval,
);
}
function endObserve() {
clearInterval(updateTimer);
}
export async function pinDiary(
project: string,
date: Date,
{ makeDiary, isOldDiary, }: DiaryInit,
): Promise<void> {
const { render, dispose } = useStatusBar();
let socket: Socket | undefined;
try {
// 今日以外の日付ページを外す
render(
{ type: "spinner" },
{ type: "text", text: unpin other diary pages...},
);
socket = await makeSocket();
for await (const { title } of listPinnedPages(project)) {
if (!isOldDiary(title, date)) continue;
await unpin(project, title, { socket });
}
// 今日の日付ページをピン留めする
const { title, header, footer } = makeDiary(date);
render(
{ type: "spinner" },
{ type: "text", text: pin "/${project}/${title}"...},
);
await pin(project, title, { socket, create: true });
// 今日の日付ページにtemplateを挿入する
render(
{ type: "spinner" },
{ type: "text", text: format "/${project}/${title}"...},
);
await patch(project, title, (lines) => [
...format(
lines.slice(1).map(line => line.text),
header,
footer,
),
], { socket });
render(
{ type: "check-circle" },
{ type: "text", text: Pinned "/${project}/${title}".},
);
} catch(e: unknown) {
render(
{ type: "exclamation-triangle" },
{ type: "text", text: e instanceof Error ?
${e.name} ${e.message} :
Unknown error! (see developper console),
},
);
console.error(e);
} finally {
if (socket) await disconnect(socket);
await sleep(1000);
dispose();
}
}
list.ts
code:list.ts
import { listPages, BasePage } from "./deps.ts";
/** 全てのピン留めされたページを取得する */
export async function* listPinnedPages(project: string, skip = 0): AsyncGenerator<BasePage> {
const { count, pages } = await ensureList(project, skip);
for (const page of pages) {
if (page.pin === 0) continue;
yield page;
}
// pinしたページこれ以上ないときは終了
if ((pages.at(-1)?.pin ?? 0) === 0) return;
yield* listPinnedPages(project, skip + 1000);
}
async function ensureList(project: string, skip: number) {
const result = await listPages(project, {
limit: 1000,
skip,
});
// login errorなどは全部例外として扱う
if (!result.ok) {
const error = new Error();
error.name = result.value.name;
error.message = result.value.message;
throw error;
}
return result.value;
}